//
//  YLRequest.swift
//  Swift-Codedase
//
//  Created by 徐先生 on 2023/2/27.
//
// https://www.jianshu.com/p/55243f4ab031
// https://cloud.tencent.com/developer/article/2167093?areaSource=102001.20&traceId=lD5yFzVi7suEQSJZY1hvy

// [iOS Swift moya框架发送数组作为请求参数（GET/POST）](https://www.jianshu.com/p/19f3d02739ff)
// [iOS swift moya的使用，二次封装，添加loading以及数据缓存](https://wsa.jianshu.io/p/9e80c29c1856)
// [SwiftProject](https://github.com/Xianlau/SwiftProject)

import Foundation
import Moya
import SwiftyJSON
import HandyJSON
import JKSwiftExtension
import Localize_Swift

// 超时时长
private var requestTimeOut: Double = 30.0

/// Closure to be executed when a request has completed.
typealias Handle = (_ result: Result<ResponseModel, RequestError>) -> Void
// 网络请求的回调 包括：网络请求的模型(code、message、data等，具体根据业务来定)
typealias RequestCallback = ((ResponseModel) -> Void)
// 网络错误的回调
typealias errorCallback = (() -> Void)

/// dataKey一般是 "data"
let responseDataKey = "data"
let responseMessageKey = "msg"
let responseCodeKey = "code"
let responseSuccessKey = "success"
let successCode: Int = 1

struct RequestError: Error {
    /// 错误码 只读
    private(set) var errCode: Int
    /// 错误信息
    private(set) var errMsg: String
    
    // json 数据,保存response.data
    var dataSources: JSON?
    // 字典类型的数据
    var dataDictionary: [String: Any] {
        get {
            dataSources?.dictionaryObject ?? [:]
        }
    }
    // 数组类型的数据
    var dataArray: [Any] {
        get {
            dataSources?.arrayObject ?? []
        }
    }
    
    init(_ code: Int, _ message: String, _ dataSources: JSON? = nil) {
        self.errCode = code
        self.errMsg = message
        self.dataSources = dataSources
    }
}

class YLRequest: NSObject {
    
    // Provider 将 Targets 映射成 Endpoints, 然后再将 Endpoints 映射成真正的 Request.
    // 这个closure存放了一些moya进行网络请求前的一些数据,可以在闭包中设置公共headers
    // 在这个闭包中, 你可以改变task,method,url,headers 或者 sampleResponse.比如,我们可能希望将应用程序名称设置到HTTP头字段中,从而用于服务器端分析.
    // 网络请求的基本设置,这里可以拿到是具体的哪个网络请求，可以在这里做一些设置
    private static let endpointClosure = { (target: TargetType) -> Endpoint in
        // 这里把endpoint重新构造一遍主要为了解决网络请求地址里面含有? 时无法解析的bug https://github.com/Moya/Moya/issues/1198
        let url = target.baseURL.absoluteString + target.path
        var task = target.task
        
        /*
         如果需要在每个请求中都添加类似token参数的参数请取消注释下面代码
         👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇
         */
        //    let additionalParameters = ["token":"888888"]
        //    let defaultEncoding = URLEncoding.default
        //    switch target.task {
        //        ///在你需要添加的请求方式中做修改就行，不用的case 可以删掉。。
        //    case .requestPlain:
        //        task = .requestParameters(parameters: additionalParameters, encoding: defaultEncoding)
        //    case .requestParameters(var parameters, let encoding):
        //        additionalParameters.forEach { parameters[$0.key] = $0.value }
        //        task = .requestParameters(parameters: parameters, encoding: encoding)
        //    default:
        //        break
        //    }
        /*
         👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆
         如果需要在每个请求中都添加类似token参数的参数请取消注释上面代码
         */
        
        var endpoint = Endpoint(
            url: url,
            sampleResponseClosure: { .networkResponse(200, target.sampleData) },
            method: target.method,
            task: task,
            httpHeaderFields: target.headers
        )
        return endpoint
    }
    
    // 网络请求的设置
    // 前面endpointClosure会把target映射为endpoint, Moya会把endpoint转换为一个真正的Request
    // RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void 就是 Endpoint 转换为 Request的一个拦截, 它还可以修改请求的结果(通过调用RequestResultClosure = (Result<URLRequest, MoyaError>))
    // 这个闭包是moya提供给我们对网络请求开始前最后一次机会对请求进行修改, 比如设置超时时间(默认是60s), 禁用cookie等
    private static let requestClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<MultiTarget>.RequestResultClosure) -> Void in
        do {
            // Modify the request however you like.(任意修改请求.)
            var request = try endpoint.urlRequest()
            // 设置请求时长
            request.timeoutInterval = requestTimeOut
            // 打印请求参数
            //            xLog("**********请求开始**********")
            //            var logInfo: [String: Any?] = [:]
            //            logInfo["URL"] = request.url!.absoluteString
            //            logInfo["HttpMethod"] = request.httpMethod!
            //
            //            // xLog("[网络请求] URL：\(request.url!)")
            //            // xLog("[网络请求] HttpMethod：\(request.httpMethod!)")
            //            if let header = request.allHTTPHeaderFields {
            //                let jsonData = JSON(header)
            //                // xLog("[网络请求] Header：\(jsonData)")
            //                logInfo["Token"] = jsonData.dictionaryObject!["token"]// as? String
            //            }
            //
            //            if let requestData = request.httpBody {
            //                let jsonData = try JSON(data: requestData)
            //                // xLog("[网络请求] 请求参数：\(jsonData)")
            //                logInfo["请求参数"] = jsonData.dictionaryObject
            //            }
            //            xLog("[网络请求] 请求参数 \(logInfo as NSDictionary)")
            
            done(.success(request))
        } catch {
            done(.failure(MoyaError.underlying(error, nil)))
        }
    }
    
    // 它就是让你告诉Moya你是否使用Stub返回数据或者怎样使用Stub返回数据
    // .never: 不使用Stub来返回模拟的网络数据,默认
    // .immediate: 马上返回Stub的数据
    // .delayed(seconds: 3): 一段时间间隔后使用Stub返回的数据
    // RequestAPI中我们实现了接口sampleData, 这个属性是返回Stub数据的。
    private static let stubClosure: (MultiTarget) -> Moya.StubBehavior = { type in
        switch type {
            //根据API判断
            //case .userInfo:
            //return .immediate
        default:
            return .never
        }
    }
    
    // NetworkActivityPlugin插件用来监听网络请求，界面上做相应的展示
    // 但这里我没怎么用这个。。。 loading的逻辑直接放在网络处理里面了
    private static let networkPlugin = NetworkActivityPlugin.init { (changeType, targetType) in
        // targetType 是当前请求的基本信息
        //        switch(changeType){
        //        case .began:
        //            print("开始请求网络")
        //
        //        case .ended:
        //            print("结束")
        //        }
    }
    
    /// 网络日志插件
    private static let networkLoggerPlugin = NetworkLoggerPlugin(configuration: .init(
        formatter: .init(
            requestData: { data in return String(data: data, encoding: .utf8) ?? "" },
            responseData: { data in return String(data: data, encoding: .utf8) ?? "" }
        ),
        logOptions: .verbose
    ))
    
    /// Moya默认有4个插件分别为：
    /// AccessTokenPlugin 管理AccessToken的插件
    /// CredentialsPlugin 管理认证的插件
    /// NetworkActivityPlugin 管理网络状态的插件
    /// NetworkLoggerPlugin 管理网络log的插件
    /// 插件，实现 pluginType 可以实现在网络请求前转菊花，请求完成结束转菊花，或者写日志等功能
    private struct PluginManager: PluginType {
        
        /// Called to modify a request before sending.(可进行数据加密等)
        func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
            return request
        }
        
        /// Called immediately before a request is sent over the network (or stubbed).（可进行网络等待，loading等）
        func willSend(_ request: RequestType, target: TargetType) {
        }
        
        /// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.（loading结束等）
        func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        }
        
        /// Called to modify a result before completion.(可进行数据解密等)
        func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
            return result
        }
    }
    
}


extension YLRequest {
    
    /// 只发网络请求，不做请求处理
    static func request(_ target: TargetType) {
        YLRequest.request(target) { result in }
    }
    
    ///  需要知道成功、失败、错误情况回调的网络请求   像结束下拉刷新各种情况都要判断
    /// - Parameters:
    ///   - target: 网络请求
    ///   - completion: 结束
    @discardableResult //当我们需要主动取消网络请求的时候可以用返回值Cancellable, 一般不用的话做忽略处理
    static func request(_ target: TargetType, completion: @escaping Handle) -> Cancellable? {
        
        // 先判断网络是否有连接 没有的话直接返回--代码略
        if !UIDevice.isNetworkConnect {
            // code = 9999 代表无网络  这里根据具体业务来自定义
            completion(.failure(.init(999999, "网络似乎出现了问题")))
            return nil
        }
        
        // 网络请求发送的核心初始化方法，创建网络请求对象
        let provider = MoyaProvider<MultiTarget>(
            endpointClosure: endpointClosure,
            requestClosure: requestClosure,
            stubClosure: stubClosure,
            plugins: [
                networkPlugin,
                PluginManager(),
                networkLoggerPlugin
            ],
            trackInflights: false
        )
        
        return provider.request(MultiTarget(target)) { result in
            switch result {
            case let .success(response):
                
                // 打印内容
                var printContent: [String: Any] = [:]
                printContent["1.URL"] = response.request?.url?.absoluteString
                printContent["2.Method"] = response.request?.httpMethod
                if let header: [String: String] = response.request?.allHTTPHeaderFields {
                    printContent["3.Token"] = header["token"]
                    // printContent["2.Header"] = header
                    let requestStartTime: String = header["requestStartTime"] ?? "0"
                    let startTime = Int(requestStartTime.sg.toInt*1000)
                    let endTime = Int(CFAbsoluteTimeGetCurrent()*1000)
                    let differenceTime = endTime - startTime
                    let time = "\(differenceTime/1000)秒\(differenceTime%1000)毫秒"
                    printContent["0.响应时间"] = time
                }
                
                if response.request?.httpMethod == "GET" {
                    // Get 网络请求时，httpBody 是没有数据的，需要从 url 中截取请求参数
                    if let uRL = URL(string: response.request?.url?.absoluteString ?? "") {
                        printContent["4.Body"] = uRL.sg.pathComponents ?? [:]
                    }
                }
                
                if let httpBody = response.request?.httpBody {
                    if let bodyString = String(data: httpBody, encoding: .utf8) {
                        if let bodyDic = bodyString.toDictionary() {
                            printContent["4.Body"] = bodyDic
                        }
                    }
                }
                
                var respModel = ResponseModel()
                if let responseString = String(data: response.data, encoding: .utf8) {
                    if let responseDic = responseString.toDictionary() {
                        // 接口返回的数据是字典
                        printContent["5.Result"] = responseDic
                        if let respM = JsonUtil.modelFromDictionary(responseDic, ResponseModel.self) {
                            respModel = respM
                        }
                        if let data = responseDic[responseDataKey] {
                            respModel.dataSources = JSON(data)
                        } else {
                            respModel.dataSources = JSON(responseDic)
                        }
                        
                        if respModel.code == -999 {
                            respModel.code = successCode
                        }
                        
                        let arr = [
                            "/faLive/startLive",
                            "/faLive/isOnLive",
                            
                            "/faUserStaff/getSelectStaffCondition",
                            "/faUserStaff/getUserStaff",
                            "/faUserStaff/getTranslatorInfo",
                            "/faAvCallSubtotal/getAvCallSubtotal",
                            "/faAvCallSubtotal/save",
                            "/faAvCallSubtotal/calculateScore",
                            "/faUnAccepted/save"
                        ]
                        
                        arr.forEach { path in
                            if let url = response.request?.url?.absoluteString, url.contains(path) {
                                // 该接口 code = 0 是 成功的, 其他失败
                                if respModel.code == 0 {
                                    respModel.code = successCode
                                }
                            }
                        }
                        
                    } else if let responseArr = responseString.toArray() {
                        // 接口返回的数据是数组
                        printContent["5.Result"] = responseArr
                        respModel.dataArray = responseArr
                        respModel.code = successCode
                        
                    } else {
                        // 接口返回的数据是其他类型的，都定位字符串
                        printContent["5.Result"] = responseString
                        respModel.dataString = responseString
                        respModel.code = successCode
                    }
                    
                    if respModel.success {
                         xLog("[网络请求] 成功： \(printContent as NSDictionary)")
                        completion(.success(respModel))
                        
                    } else {
                        xLog("[网络请求] 失败： \(printContent as NSDictionary)")
                        completion(.failure(.init(respModel.code, respModel.message, respModel.dataSources)))
                    }
                    
                } else {
                    xLog("[网络请求] 失败： \(printContent as NSDictionary)")
                    completion(.failure(.init(respModel.code, respModel.message, respModel.dataSources)))
                }
                
            case let .failure(error as NSError):
                xLog("[网络请求] 失败：\(error.domain)")
                completion(.failure(.init(error.code, "网络请求超时，请重试。".localized())))
            }
        }
    }
    
}
